package com.wontube.smr.bo.sites.crackle;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.ibatis.session.SqlSession;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import com.wontube.client.extension.utils.DateUtils;
import com.wontube.client.extension.utils.NumberUtils;
import com.wontube.client.extension.utils.StringUtils;
import com.wontube.core.dao.mybatis.QueryExample;
import com.wontube.core.pub.AppEnvironment;
import com.wontube.smr.bo.constants.SiteConstants;
import com.wontube.smr.client.model.po.Movie;
import com.wontube.smr.client.model.po.MovieGenre;
import com.wontube.smr.client.model.po.TVEpisode;
import com.wontube.smr.client.model.po.TVSeason;
import com.wontube.smr.client.model.po.TVShow;
import com.wontube.smr.client.model.po.TVShowGenre;
import com.wontube.smr.dao.mapper.MovieGenreMapper;
import com.wontube.smr.dao.mapper.MovieMapper;
import com.wontube.smr.dao.mapper.TVEpisodeMapper;
import com.wontube.smr.dao.mapper.TVSeasonMapper;
import com.wontube.smr.dao.mapper.TVShowGenreMapper;
import com.wontube.smr.dao.mapper.TVShowMapper;
import com.wontube.smr.pub.DefaultDBHelper;

/**
 * www.crackle.com
 * @author tanggf(2012-7-4)
 *
 */
public class CrackleObtainBO {
	private final static int READ_TIME_OUT = 5000;	
	private final static String WEBSITE = "http://www.crackle.com";		
	private final static String MOVIE_URL_FORMAT = WEBSITE + "/shows/shows.aspx?cl=o%3D2%26fa%3D82%26fs%3D%26fab%3D%26fg%3D%26fry%3D&st=a&fb=f&p={}";
	private final static String TV_URL_FORMAT = WEBSITE + "/shows/shows.aspx?cl=o%3D2%26fa%3D114%26fs%3D%26fab%3D%26fg%3D%26fry%3D&st=a&fb=f&p={}";
	private final static String TV_EP_LIST_FORMAT = WEBSITE + "/browse/MediaListChunk.aspx?ci={}&id={}&l=True&dci=False&m=False&startindex=0&itemCount={}";
	private final static String PLAYING_URL_FORMAT = WEBSITE + "/Play/NowPlaying.aspx?id={}&channelId={}";	
	private final static String  CHANNLE_TIP_URL_FORMAT = WEBSITE + "/browse/ToolTip.aspx?id=0&channelId={}";
	private final static String  EP_TIP_URL_FORMAT = WEBSITE + "/browse/ToolTip.aspx?id={}";
	
	private final static Pattern EP_TOTAL_PATTERN = Pattern.compile("Videos\\((\\d+)\\)");
	private final static Pattern SEASON_ID_PATTERN = Pattern.compile("id=(\\d+)&");
	private final static Pattern WATCH_VIDEO_PATTERN = Pattern.compile("UpdateWatchPage\\((\\d+),(\\d+)\\)");
	private final static Pattern SEASON_AND_EP_PATTERN = Pattern.compile("Season.*?(\\d+).+Episode.*?(\\d+)");
	private final static Pattern EP_DURATION_PATTERN = Pattern.compile("<span class='duration'>(.+)</span>");
	
	public static int saveMovies() throws IOException{
		int count = 0;
		//第一页
		int pageCount = 1;
		for (int page = 0; page < pageCount; page++) {			
			System.err.println("movie round: " + page);
			String url = StringUtils.format(MOVIE_URL_FORMAT, page);
			URL uri = new URL(url);
			Document doc = Jsoup.parse(uri, 3000);
			//修正页数
			if(page == 0){
				pageCount = doc.select("#pagerTop > a").size() - 1;
			}
			
			List<Movie> movies = grabMovies(doc);			
			if(movies.size() < 1){
				continue;
			}			
			
			SqlSession session = DefaultDBHelper.getBatchSqlSession();
			Date now = DateUtils.getNow();
			try {
				MovieMapper mapper = session.getMapper(MovieMapper.class);
				MovieGenreMapper genreMapper = session.getMapper(MovieGenreMapper.class);
				for (Movie movie : movies) {
					movie.setSortIndex(++count);
					movie.setVersion(now);
												
					String movieId = movie.getId();					
					Movie tmpMovie = mapper.selectByPrimaryKey(movieId);
					if(tmpMovie != null){
						mapper.updateByPrimaryKey(movie);
					}else{
						mapper.insert(movie);
					}
					
					//movie genre 关联
					String genreName = movie.getGenre();
					if(genreName != null){
						String[] genres = movie.getGenre().split("\\s*,\\s*");
						for (String genre : genres) {
							QueryExample ex = new QueryExample();
							ex.or().addCriterion("movie_genre.movie_id=", movieId)
									.addCriterion("movie_genre.genre_name=", genre);
							
							if(genreMapper.countByExample(ex) < 1){
								//增加关联
								MovieGenre movieGenre = new MovieGenre();
								movieGenre.setId(UUID.randomUUID().toString());
								movieGenre.setMovieId(movieId);
								movieGenre.setGenreName(genre);
								
								genreMapper.insert(movieGenre);
							}
						}		
					}					
				}
				
				session.commit();
			} finally {
				session.close();
			}				
		}		
		
		return count;
	}	

	private static List<Movie> grabMovies(final Document doc) throws IOException{
		List<Movie> movies = new ArrayList<Movie>();
		Elements  els = doc.select("ul.showsThumbnail > li > div.thumbnail > a");
		if( ! els.isEmpty()){		
			for (Element el : els) {
				String href = el.attr("href");
				Movie movie = grabMovieDetail(href);
				if(movie != null){
					movies.add(movie);
				}
			}
		}
		return movies;
	}
	
	private static Movie grabMovieDetail(String href) throws IOException{
		String pageUrl = WEBSITE + href;
		Document doc = Jsoup.parse(new URL(pageUrl), READ_TIME_OUT);
		if(doc != null){
			Element cEl = doc.getElementById("channelId");
			String cid = cEl.val();
			Element mEl = doc.getElementById("currentMediaID");
			String vid = mEl.html();
			
			Movie movie = new Movie();
			movie.setItemUrl(pageUrl);
			//setid
			movie.setId(SiteConstants.ID_PREFIX_CRACKLE + vid);
			movie.setMovieType(Movie.TYPE_MOVIE);
			movie.setWebsite(SiteConstants.WEBSITE_CRACKLE);
			movie.setVideoId(vid);
			movie.setArea(cid);
			
			//banner img
			String burl = doc.select("#content-about > div.thumbArea > div.thumbnail > img").attr("src");
			movie.setBannerImgUrl(burl);
			
			//actors, director(可能只出现其中一个)
			Elements infos = doc.select("#content-about > div.info > div");
			for (Element el : infos) {
				String text = el.text();
				if(text.startsWith("Cast:")){
					movie.setActors(text.substring(5));
				}else if(text.startsWith("Director:")){
					movie.setDirector(text.substring(9));
				}
			}
						
			String playingUrl = StringUtils.format(PLAYING_URL_FORMAT, vid, cid);				
			//取时长, 发布年份, 描述, title, genre
			doc = Jsoup.parse(new URL(playingUrl), READ_TIME_OUT);			
			if(doc != null){				
				String img = doc.select("div.thumbarea > div.thumbnail > img").attr("src");
				movie.setThumbnail(img);
				
				String title = doc.select("div > div.title > h1").eq(0).text();
				movie.setTitle(title);
				
				String desc = doc.select("#description").html();
				movie.setDescription(desc);
				
				Elements spans = doc.select("div.description > div > span");				
				String dur = (spans.get(1).ownText()).substring(2);				
				String rel = (spans.get(2).ownText()).substring(1);				
				movie.setDuration(StringUtils.toSeconds(dur));
				if(rel.indexOf("/") < 0){
					//处理只有年份的情况
					rel = "1/1/" + rel;
				}			
				movie.setPublishTime(DateUtils.parse(rel));			
				
				//genre
				Elements gel = spans.select(".genre");
				if(gel.size() > 0){
					movie.setGenre(gel.get(0).ownText().substring(1));
				}
			}	
			
			return movie;
		}
		return null;
	}
	
	public static int saveTVs() throws IOException{
		int count = 0;
		
		//第一页
		int pageCount = 1;
		for (int page = 0; page < pageCount; page++) {			
			System.err.println("tv round: " + page);
			String url = StringUtils.format(TV_URL_FORMAT, page);
			URL uri = new URL(url);
			Document doc = Jsoup.parse(uri, 3000);
			//修正页数
			if(page == 0){
				pageCount = doc.select("#pagerTop > a").size() - 1;
			}			
			
			count += grabAndSaveTV(doc, count);			
		}				
		return count;
	}
	
	private static int grabAndSaveTV(Document doc, int count) throws IOException{
		//tv show href
		Elements els = doc.select("ul.showsThumbnail > li.channel > div.thumbnail > a");
		int index = count;
		for (Element el : els) {
			String href = el.attr("href");				
			//thumbnail
			String thumbnail = el.select("img").attr("src");		
			
			TVShow show = new TVShow();
			show.setItemUrl(WEBSITE + href);
			show.setThumbnail(thumbnail);			
			show.setWebsite(SiteConstants.WEBSITE_CRACKLE);	
			show.setSortIndex(++index);
			saveTVDetail(show);
		}
		return els.size();
	}

	private static void saveTVDetail(TVShow show) throws IOException{
		String showUrl = show.getItemUrl();
		Document doc = Jsoup.parse(new URL(showUrl), READ_TIME_OUT);
		String cid = doc.getElementById("channelId").val();
		String vid = doc.getElementById("currentMediaID").text();		
		show.setId(SiteConstants.ID_PREFIX_CRACKLE + vid);		
		show.setVideoId(vid);		
		
		//episode count, desc, actor, director, banner image, 
		grabTVInfo(doc, show, cid);		
		
		//season count 信息
		//sc可能为0
		Elements els = doc.select("#content-videos > div.folder > div.header > div.playlistTab > ul > li > a");		
		int sc = els.size();
		show.setSeasonCount(sc);
		
		List<TVEpisode> showEps = new ArrayList<TVEpisode>();
		if(sc == 0){
			//只有一季一集						
			showEps.add(parseFirstEpisode(vid, cid));
		}else{			
			//循环每一季
			showEps = grabTVSeasons(els, show, cid);
		}
		
		//使用第一集更新tvshow字段		
		show.setAirBeginDate(showEps.get(0).getAirDate());		
		saveTV2DB(show, showEps);
	}
	
	private static TVEpisode parseFirstEpisode(String vid, String cid) throws IOException {		
		String url = StringUtils.format(PLAYING_URL_FORMAT, vid, cid);
		Document doc = Jsoup.parse(new URL(url), READ_TIME_OUT);
		TVEpisode ep = new TVEpisode();
		String img = doc.select("div.thumbarea > div.thumbnail > img").attr("src");
		ep.setThumbnail(img);
		
		String title = doc.select("div > div.title > h2").text();
		ep.setEpisodeName(title);
		String ee = doc.select("div >div.title > h3").text();
		Matcher m = SEASON_AND_EP_PATTERN.matcher(ee);	
		if(m.find()){
			int season = NumberUtils.parseInt(m.group(1));
			int part = NumberUtils.parseInt(m.group(2));
			ep.setSeason(season);
			ep.setPart(part);
		}
		
		String desc = doc.getElementById("description").html();
		ep.setIntroduction(desc);
		
		Elements els = doc.select("div >div.description > div > span");
		for (Element el : els) {
			if(el.hasClass("genre")){
				String g = (el.ownText()).substring(1);
				ep.setGenre(g);
			}else{
				String text = el.text();
				if(text.startsWith("Duration:")){
					String dur = text.substring(10);
					ep.setDuration(NumberUtils.parseInt(dur));
				}else if(text.startsWith("Release:")){
					String rel = text.substring(9);
					if(rel.indexOf("/") < 0){
						rel = "1/1/" + rel;
					}
					ep.setAirDate(DateUtils.parse(rel));
				}
			}			
		}
		return ep;
	}

	private static void grabTVInfo(Document doc, TVShow show, String cid) throws IOException {
		//episode count 信息
		String t = doc.select("#tabs > li").first().child(0).text();
		Matcher m = EP_TOTAL_PATTERN.matcher(t);
		if(m.matches()){
			t = m.group(1);
			show.setEpisodeCnt(NumberUtils.parseInt(t));
		}		
		
		//desc, actor, director, banner image, 
		String desc = doc.select("#content-about > div.description > p").html();
		show.setDescription(desc);
		String burl = doc.select("#content-about > div.thumbArea > div.thumbnail > img").attr("src");
		show.setBannerImgUrl(burl);
		
		//actors, director, genre
		String infoUrl = StringUtils.format(CHANNLE_TIP_URL_FORMAT, cid);
		Document idoc = Jsoup.parse(new URL(infoUrl), READ_TIME_OUT);		
		String title = idoc.select("div.tooltip > div.content > div.title > p.channel-title").text();
		show.setName(title);
		
		Elements peles = idoc.select("div.tooltip > div.content > div.item-info > p");
		String g = (peles.get(1).ownText()).substring(2);
		show.setGenre(g);
		
		String actors = idoc.select("div.tooltip > div.content > div.item-desc > p").eq(2).text();
		show.setActors(actors);
	}
	
	private static List<TVEpisode> grabTVSeasons(Elements els, TVShow show, String cid) throws IOException{
		List<TVEpisode> showEps = new ArrayList<TVEpisode>();
		//每一季的信息
		for (Element el : els) {
			String hr = el.attr("href");
			//获取season id
			Matcher sm = SEASON_ID_PATTERN.matcher(hr);
			if(sm.find()){
				String sid = sm.group(1);
				//获取总数
				Document doc = Jsoup.parse(new URL(WEBSITE + hr), READ_TIME_OUT);
				String total = doc.getElementById("mediaCount" + sid).text();
				
				//获取整季数据(json)
				String infoUrl = StringUtils.format(TV_EP_LIST_FORMAT, cid, sid, total);
				JsonArray jsonArray = (JsonArray)parseToJsonElement(infoUrl);		
				if(jsonArray != null){
					for (JsonElement jsonElement : jsonArray) {							
						String content = jsonElement.getAsJsonObject().get("content").getAsString();
						Matcher cm = WATCH_VIDEO_PATTERN.matcher(content);
						if(cm.find()){
							String vid = cm.group(1);
							String oid = cm.group(2);
							TVEpisode ep = new TVEpisode();
							ep.setUrl(show.getItemUrl() + "#id=" + vid + "&ml=o%3D12%26fpl%3D" + oid + "%26fx%3D");									
							ep.setWebsite(SiteConstants.WEBSITE_CRACKLE);
							
							Matcher dm = EP_DURATION_PATTERN.matcher(content);
							if(dm.find()){
								String d = dm.group(1);
								ep.setDuration(StringUtils.toSeconds(d));
							}							
										
							//抓取episode详情
							grabEpisodeInfo(show, ep, vid);
							
							showEps.add(ep);
						}
					}
				}
			}
		}
		
		return showEps;
	}
	

	private static TVEpisode grabEpisodeInfo(TVShow show, TVEpisode ep, String vid) throws IOException {	
		String url = StringUtils.format(EP_TIP_URL_FORMAT, vid);
		Document doc = Jsoup.parse(new URL(url), READ_TIME_OUT);
		if(doc != null){		
			String img = doc.select("div.tooltip > div.content > div.thumbnail > a > img").eq(0).attr("src");
			ep.setThumbnail(img);
			String name = doc.select("div.tooltip > div.content > div.title > p").get(1).ownText();
			ep.setEpisodeName(name);
			
			String desc = doc.select("div.tooltip > div.content > p.item-desc").eq(0).text();
			ep.setIntroduction(desc);
			
			Elements infos = doc.select("div.tooltip > div.content > div.item-info > p");
			String cc = infos.get(0).text();
			Matcher mm = SEASON_AND_EP_PATTERN.matcher(cc);
			int season = 1;
			int part = 1;
			if(mm.find()){			
				season = NumberUtils.parseInt(mm.group(1));
				part = NumberUtils.parseInt(mm.group(2));
				ep.setSeason(season);
				ep.setPart(part);				
			}
			String re = (infos.get(3).ownText()).substring(2);
			if(re.indexOf("/") < 0){
				re = "1/1/" + re;
			}			
			ep.setAirDate(DateUtils.parse(re));
			
			String showId = show.getId();
			ep.setShowId(showId);
			String epId = showId + "E" + season + "E" + part;
			ep.setId(epId);		
		}
		return null;
	}

	private static JsonElement parseToJsonElement(String infoUrl) throws IOException{
		HttpURLConnection infoConn = (HttpURLConnection) new URL(infoUrl).openConnection();
		infoConn.connect();		
		
		InputStreamReader reader = new InputStreamReader(infoConn.getInputStream(), AppEnvironment.charsetName);
		try {
			JsonParser parser = new JsonParser();
			return parser.parse(reader);
		} catch (JsonParseException ex) {
			return null;
		} finally {
			reader.close();
			infoConn.disconnect();
		}
	}
	
	

	private static void saveTV2DB(TVShow show, List<TVEpisode> eps) {
		SqlSession session = DefaultDBHelper.getBatchSqlSession();
		try {					
			// 时间戳
			Date now = DateUtils.getNow();	
			show.setVersion(now);
			
			String showId = show.getId();
			
			//tv show
			TVShowMapper tvMapper = session.getMapper(TVShowMapper.class);	
			
			TVShow tmpShow = tvMapper.selectByPrimaryKey(showId);
			if(tmpShow != null){				
				tvMapper.updateByPrimaryKey(show);				
			}else{
				tvMapper.insert(show);
			}
			
			//tv show 关联
			TVShowGenreMapper genreMapper = session.getMapper(TVShowGenreMapper.class);
			String[] genres = show.getGenre().split("\\s*,\\s*");
			for (String genre : genres) {			
				QueryExample queryEx = new QueryExample();
				queryEx.or().addCriterion("tvshow_genre.show_id=", showId)
									.addCriterion("tvshow_genre.genre_name=", genre);
				if(genreMapper.countByExample(queryEx) == 0){
					// genre 未关联
					TVShowGenre tvShowGenre = new TVShowGenre();
					tvShowGenre.setId(UUID.randomUUID().toString());
					tvShowGenre.setShowId(showId);
					tvShowGenre.setGenreName(genre);
					genreMapper.insert(tvShowGenre);
				}
			}		
			
			//episode
			TVEpisodeMapper epMapper = session.getMapper(TVEpisodeMapper.class);
			for (TVEpisode ep : eps) {
				QueryExample ex = new QueryExample();
				ex.or().addCriterion("tv_episode.show_id = ", ep.getShowId())
						.addCriterion("tv_episode.season = ", ep.getSeason())
						.addCriterion("tv_episode.part = ", ep.getPart());
				List<TVEpisode> listMovieInfo = epMapper.selectWith(ex);
				
				ep.setVersion(now);
				if (listMovieInfo.size() > 0) {
					//update
					epMapper.updateByPrimaryKey(ep);
				} else {
					//insert
					epMapper.insert(ep);
				}
			}
			
			// 统计seasons
			QueryExample seasonEx = new QueryExample();
			seasonEx.or().addCriterion("tv_season.show_id = ", showId);
			List<Integer> seasons = epMapper.selectSeasons(showId);
			TVSeasonMapper seasonMapper = session.getMapper(TVSeasonMapper.class);
			seasonMapper.deleteByExample(seasonEx);
			for (Integer seasonNo : seasons) {
				seasonEx.clear();
				seasonEx.or().addCriterion("tv_season.show_id = ", showId)
					.addCriterion("tv_season.season_no = ", seasonNo);
				if (seasonMapper.countByExample(seasonEx) < 1) { // 不存在
					TVSeason season = new TVSeason();
					season.setId(UUID.randomUUID().toString());
					season.setShowId(showId);
					season.setSeasonNo(seasonNo);
					season.setVersion(Calendar.getInstance().getTime());
					seasonMapper.insert(season);
				}
			}
			
			session.commit();
		} finally {
			session.close();
		}
	}
	
	public static void main(String[] args) throws IOException {
		saveTVs();
	}
}
